package pl.puntin.punclient.controller;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.UnknownHostException;
import java.rmi.ConnectException;
import java.util.HashMap;
import java.util.Map;

import pl.puntin.punclient.messages.AddRoomFalseMessage;
import pl.puntin.punclient.messages.AddRoomOkMessage;
import pl.puntin.punclient.messages.ChatMessage;
import pl.puntin.punclient.messages.ConnectionLostMessage;
import pl.puntin.punclient.messages.CreateRoomMessage;
import pl.puntin.punclient.messages.ImageMessage;
import pl.puntin.punclient.messages.JoinRoomMessage;
import pl.puntin.punclient.messages.JoinRoomOkMessage;
import pl.puntin.punclient.messages.LoginFalseMessage;
import pl.puntin.punclient.messages.LoginMessage;
import pl.puntin.punclient.messages.LoginOkMessage;
import pl.puntin.punclient.messages.Message;
import pl.puntin.punclient.messages.NotJoinRoomMessage;
import pl.puntin.punclient.messages.NotStartNewGameMessage;
import pl.puntin.punclient.messages.PunForDrawerMessage;
import pl.puntin.punclient.messages.PunMessage;
import pl.puntin.punclient.messages.RoomListMessage;
import pl.puntin.punclient.messages.RoomListPleaseMessage;
import pl.puntin.punclient.messages.StartNewGameOkMessage;
import pl.puntin.punclient.messages.UnknowMessage;
import pl.puntin.punclient.networkadapter.NetworkAdapter;
import pl.puntin.punclient.view.View;

/** 
 *	Klasa zarzadza komunikacja miedzy klientem 
 *  a serwerem, porzez obsluge przychdzacych
 *  wiadomosci(wiadomosci ktore sa w kolejce
 *  blokujacej, nie te ktore bezposrednio pochadza
 *  z sieci), a takze przez obsluge wydarzen
 *  pochdzacych z widoku.
 *  
 *  @author kakus
 */
public class Controller
{
	/**
	 * Widok w trakcie logowania.
	 */
	private final View view;
	/**
	 * Widok w trakcie szukania pokoju.
	 */
	//private RoomView rView;
	/**
	 * Widok w trakcie gry.
	 */
	//private GameView gView; 
	/**
	 * Login gracza
	 */
	private String login;
	
	/**
	 * Obiekt zajmujacy sie obsluga sieci 
	 */
	private NetworkAdapter networkAdapter;
	
	/**
	 * Mapuje wiadomosci na odpowiednie obiekty ktore potrafia
	 * obsluzyc dana wiadomosc.
	 */
	private final Map<Type, MessageHandler> messageHandlerMap;
	
	public enum ApplicationState
	{
		/*
		 *		1 aplikacja startuje
		 *		 <- C_LOGIN
		 *		2 oczekiwanie na polaczenie 
		 *		-> S_LOGIN_OK
		 *		3 oczekiwanie na liste pokoji
		 *		-> S_ROOM_LIST
		 *		4 oczekiwanie na wybor pokoju lub stworzenie nowego
		 *		-> S_JOIN_ROOM_OK || S_ADD_ROOM_OK
		 *		5 gra
		 *		
		 *		//TODO stany aplikcaji
		 */
		JUST_STARTED,
		TRYING_TO_CONNECT,
		WAITING_FOR_ROOM_LIST,
		JOINING_GAME
		
	}
	
	/**
	 * Stan w jakim znajduje sie aplikacja 
	 */
	ApplicationState applicationState = ApplicationState.JUST_STARTED;
	
	public Controller()
	{
		messageHandlerMap = new HashMap<Type, MessageHandler>();
		
		/*
		 * Ponizej sa rejestrowane klasy(no w prawdzie to obiekty)
		 * ktore obsluguja dane wiadomosci.
		 */
		
		messageHandlerMap.put(ChatMessage.class, new ChatMessageHandler());
		messageHandlerMap.put(UnknowMessage.class, new UnknowMessageHandler());
		messageHandlerMap.put(ConnectionLostMessage.class, new ConnectionLostHandler());
		messageHandlerMap.put(LoginOkMessage.class, new LoginOkHandler());
		messageHandlerMap.put(ImageMessage.class, new ImageMessageHandler());
		messageHandlerMap.put(PunMessage.class, new PunMessageHandler());
		messageHandlerMap.put(RoomListMessage.class, new RoomListHandler());
		messageHandlerMap.put(LoginFalseMessage.class, new LoginFalseHandler());
		messageHandlerMap.put(AddRoomOkMessage.class, new AddRoomHandler());
		messageHandlerMap.put(AddRoomFalseMessage.class, new NotAddRoomHandler());
		messageHandlerMap.put(NotJoinRoomMessage.class, new NotJoinRoomHandler());
		messageHandlerMap.put(JoinRoomOkMessage.class, new JoinRoomOkHandler());
		messageHandlerMap.put(StartNewGameOkMessage.class, new StartNewGameOkHandler());
		messageHandlerMap.put(NotStartNewGameMessage.class, new NotStartNewMessageHandler());
		messageHandlerMap.put(PunForDrawerMessage.class, new PunForDrawerHandler());
		messageHandlerMap.put();
		view = new View(this);
	}
	
	/**
	 * Glowna petla programu. Odbierz wiadomosci z kolejki
	 * i wywolaj odpowiednia funkcje obslugujaca dana wiadomosc.
	 */
	public void start()
	{
		while( true )
		{
			try
			{
				Message message = networkAdapter.receive();
				
				messageHandlerMap.get(message.getClass()).handleMessage(message);
			}
			catch (InterruptedException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	
	/**
	 * Probuje sie zalgowac do serwera
	 * @param host
	 * @param port
	 * @param login
	 */
	public void loginToServer(final String host, final int port, final String login)
	{
		switch(applicationState)
		{
		case JUST_STARTED:
			try
			{
				networkAdapter = new NetworkAdapter(host, port);
				networkAdapter.startListen();
				networkAdapter.startSending();
				view.displayLogMessage("Udalo sie podlaczyc do serwera");
				
				networkAdapter.send(new LoginMessage(login));
				view.displayLogMessage("Wyslalem login do serwera, czekam na odpowiedz ...");
				applicationState = ApplicationState.TRYING_TO_CONNECT;
			}
			catch (ConnectException e)
			{
				// TODO: Blad podlaczenia sie do serwera !
				e.printStackTrace();
			}
			catch (UnknownHostException e)
			{
				// TODO Nieznany host
				e.printStackTrace();
			}
			catch (IOException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			catch (InterruptedException e)
			{
				// TODO blad podczas odkladania wiadmosci do wyslania
				e.printStackTrace();
			}
			
		case TRYING_TO_CONNECT:
			//TODO probja polaczenia kiedy na ostatnie polaczenie nie bylo odpowiedzi
			break;
			
		default:
			view.displayErrorMessage("Blad w Just_started");
			break;
		}
	}
	
	public void creatRoom()
	{
		try {
			networkAdapter.send(new CreateRoomMessage());
		} catch (InterruptedException e) {
			view.displayErrorMessage("Nie mozesz stworzyc pokoju, sprobuj jeszcze raz.");
			e.printStackTrace();
		}
	}
	
	public void joinRoom(String roomId)
	{
		try {
			networkAdapter.send(new JoinRoomMessage(roomId));
		} catch (InterruptedException e) {
			view.displayErrorMessage("Nie udało sie stworzyc pokoju. Sprobuj ponownie");
			e.printStackTrace();
		}
	}
	
	public void logout()
	{
		//networkAdapter.send(new LogoutMessage());
	}
	
	public void getRoomList()
	{
		try {
			networkAdapter.send(new RoomListPleaseMessage());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public void sendImage(final BufferedImage bufferedImage)
	{
		try
		{
			networkAdapter.send(new ImageMessage(bufferedImage));
			view.displayLogMessage("Wysylam obrazek");
		}
		catch (InterruptedException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Inteface do obslugi wiadomosci. Poniewaz w javie nie ma
	 * wskaznikow na funkcje trzeba tworzyc obiekty i na rozne
	 * sposoby implementowac funkcje handleMessage
	 * 
	 * @author kakus
	 *
	 */
	private interface MessageHandler
	{
		public void handleMessage(final Message message);
	}
	
	/**
	 * Klasa obslugujaca wiadomosc tekstowa.
	 * 
	 * @author kakus
	 *
	 */
	class ChatMessageHandler implements MessageHandler
	{
		@Override
		public void handleMessage(final Message message)
		{
			ChatMessage chatMessage = (ChatMessage)message;
			view.displayChatMessage(chatMessage.text);
		}
	}
	
	/**
	 * Obsluguje wiadomosci o nieznany typie
	 * @author kakus
	 *
	 */
	class UnknowMessageHandler implements MessageHandler
	{

		@Override
		public void handleMessage(final Message message)
		{
			UnknowMessage unknowMessage = (UnknowMessage)message;
			view.displayErrorMessage("Nie znany typ wiadmosci: "+unknowMessage.messageCode);
		}
		
	}
	/**
	 * Obs�uguje wiadomosc o sukcesie w stworzeniu pokoju
	 * @author luk
	 *
	 */
	class RoomListHandler implements MessageHandler
	{

		@Override
		public void handleMessage(final Message message)
		{
			view.roomListDisplay((RoomListMessage) message);
		}
		
	}
	
	/**
	 * Utrata polaczenia
	 * @author kakus
	 *
	 */
	class ConnectionLostHandler implements MessageHandler
	{
		
		@Override
		public void handleMessage(final Message message)
		{
			view.displayErrorMessage("Connection lost");
		}
		
	}
	
	class ImageMessageHandler implements MessageHandler
	{

		@Override
		public void handleMessage(final Message message)
		{
			view.displayLogMessage("Odebralem obrazek");
			view.displayImageMessage((ImageMessage)message);
		}
		
	}
	
	class PunMessageHandler implements MessageHandler
	{
		
		@Override
		public void handleMessage(final Message message)
		{
			view.youAreDrawer();
		}
		
	}
	
	
	/**
	 * Serwer mowi ze polaczenie jest okey :)
	 * @author kakus
	 *
	 */
	class LoginOkHandler implements MessageHandler
	{
		
		@Override
		public void handleMessage(final Message message)
		{
			
			switch (applicationState)
			{
			case TRYING_TO_CONNECT:
				try
				{
					view.displayLogMessage("login okej");
					//TODO udalo sie polaczyc
					networkAdapter.send(new RoomListPleaseMessage());
					applicationState = ApplicationState.WAITING_FOR_ROOM_LIST;
				}
				catch (InterruptedException e)
				{
					view.displayErrorMessage("Watek upadl jak troja i nei wyslal prosby o liste pokoii");
					e.printStackTrace();
				}
				break;

			default:
				view.displayErrorMessage("Got message LOGIN_OK, but application wasn't waiting for it.");
				break;
			}
		}
		
	}
	
//	/**
//	 * Metoda obs�guj�ca widok logowania.
//	 * 
//	 *  @author Luk
//	 */
//	private void loginState()
//	{
//		lView = new LoginView();
//		lView.loginButton.addActionListener(new ActionListener() 
//		{
//            @Override
//			public void actionPerformed(final ActionEvent evt) 
//            {
//                loginButtonActionPerformed(evt);
//            }
//        });
//	}
	
//    private void loginButtonActionPerformed(final ActionEvent evt)
//    {
//    	login = lView.getLogin();
//    	nAdapter = new NetworkAdapter(lView.getIp());
//    }
}